package com.gs.netty.support.resolver;


import com.gs.netty.core.handler.method.ControllerAdviceBean;
import com.gs.netty.core.handler.method.HandlerMethod;
import com.gs.netty.core.resolver.AbstractHandlerExceptionResolver;
import com.gs.netty.core.resolver.HandlerMethodReturnValueHandler;
import com.gs.netty.core.resolver.args.HandlerMethodArgumentResolver;
import com.gs.netty.support.handler.method.InvocableHandlerMethod;

import com.gs.netty.websocket.remoting.req.NativeWebSocketRequest;
import com.gs.netty.websocket.remoting.req.Request;
import com.gs.netty.websocket.remoting.req.WebSocketRequest;
import com.gs.netty.websocket.remoting.resp.WebSocketResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 异常解析器
 */

public class ExceptionHandlerExceptionResolver extends
        AbstractHandlerExceptionResolver<WebSocketRequest, WebSocketResponse> implements ApplicationContextAware {

    protected final Log logger = LogFactory.getLog(getClass());

    private ApplicationContext applicationContext;

    /**
     * 参数解析器
     */
    protected HandlerMethodArgumentResolver argumentResolvers;
    /**
     * 结果处理器
     */
    protected HandlerMethodReturnValueHandler returnValueHandlers;


    /**
     * 局部异常解析
     */
    private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap<>(64);

    /**
     * 全局异常解析
     */
    private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =
            new LinkedHashMap<>();


    protected void doResolveHandlerMethodException(WebSocketRequest request, WebSocketResponse response, HandlerMethod handlerMethod, Exception exception) {

        InvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);

        NativeWebSocketRequest nativeWebSocketRequest = new NativeWebSocketRequest(request, response);

        if (argumentResolvers != null) {
            // 设置参数解析器
            exceptionHandlerMethod.setArgumentResolvers(argumentResolvers);
        }


        if (returnValueHandlers != null) {
            // 设置参数解析器
            exceptionHandlerMethod.setReturnValueHandlers(returnValueHandlers);
        }

        ArrayList<Throwable> exceptions = new ArrayList<>();
        try {
            Throwable exToExpose = exception;

            while (exToExpose != null) {
                exceptions.add(exToExpose);
                Throwable cause = exToExpose.getCause();
                exToExpose = (cause != exToExpose ? cause : null);
            }
            // 参数
            Object[] arguments = new Object[exceptions.size() + 1];
            exceptions.toArray(arguments);  // efficient arraycopy call in ArrayList
            arguments[arguments.length - 1] = handlerMethod;

            exceptionHandlerMethod.invokeAndHandle(nativeWebSocketRequest, arguments);
        }
        catch (Throwable invocationEx) {
            // 还出现异常就不做处理打印日志了
            if (!exceptions.contains(invocationEx) && logger.isWarnEnabled()) {
                logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
            }
        }
    }

    @Override
    protected void initExceptionHandlerAdviceCache() {

        if (this.applicationContext == null) {
            return;
        }
        // 找到带有 @CommandControllerAdvice  注解的
        List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(applicationContext);
        for (ControllerAdviceBean adviceBean : adviceBeans) {
            Class<?> beanType = adviceBean.getBeanType();

            if (beanType == null) {
                throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
            }
            ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
            if (resolver.hasExceptionMappings()) {
                this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
            }

        }

    }

    @Override
    protected void doResolveException(
            WebSocketRequest request, WebSocketResponse response,  Object handler, Exception ex) {

        HandlerMethod handlerMethod = (handler instanceof HandlerMethod ? (HandlerMethod) handler : null);
        doResolveHandlerMethodException(request, response, handlerMethod, ex);
    }


    @Override
    protected void initArgumentResolvers() {
        if (this.argumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
            resolvers.add(new RequestResponseMethodArgumentResolver());
            resolvers.add(new RequestBodyJsonMethodProcessor());
            this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addArgumentResolvers(resolvers);
        }
    }

    @Override
    protected void initReturnValueHandlers() {
        if (this.returnValueHandlers == null) {
            List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<>();
            returnValueHandlers.add(new ExceptionHandlerMethodReturnValueHandler());
            this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(returnValueHandlers);
        }
    }

    private InvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
        Class<?> handlerType = null;

        if (handlerMethod != null) {
            // 局部异常处理
            // Local exception handler methods on the controller class itself.
            // To be invoked through the proxy, even in case of an interface-based proxy.
            // 获取处理器方法类型
            handlerType = handlerMethod.getBeanType();
            ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
            if (resolver == null) {
                // 为这个处理器创建异常解析器
                resolver = new ExceptionHandlerMethodResolver(handlerType);
                this.exceptionHandlerCache.put(handlerType, resolver);
            }

            //找到异常方法
            Method method = resolver.resolveMethod(exception);
            if (method != null) {
                // 不为null 证明该异常找到它的局部方法处理类
                return new InvocableHandlerMethod(handlerMethod.getBean(), method);
            }

            // For advice applicability check below (involving base packages, assignable types
            // and annotation presence), use target class instead of interface-based proxy.
            if (Proxy.isProxyClass(handlerType)) {
                handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
            }
        }

        // 代码执行到这证明没有局部异常处理
        for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
            ControllerAdviceBean advice = entry.getKey();
            if (advice.isApplicableToBeanType(handlerType)) {
                ExceptionHandlerMethodResolver resolver = entry.getValue();
                Method method = resolver.resolveMethod(exception);
                if (method != null) {
                    return new InvocableHandlerMethod(advice.resolveBean(), method);
                }
            }
        }


        return null;
    }

    @Override
    public boolean support(Object o) {
        return Request.class.isAssignableFrom(o.getClass());
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

}
